#include <windows.h>
#include <MSR_NuiApi.h>

HWND g_hWnd = NULL;

const int g_nWidth = 640;
const int g_nHeight = 480;

HANDLE g_hProcessStopEvent	= NULL;
HANDLE g_hNextSkeletonEvent	= NULL;
HANDLE g_hVideoStream		= NULL;

const int g_nEventCount = 2;
HANDLE g_hEvents[g_nEventCount] = {NULL, };

HDC		g_SkeletonDC = NULL;
HPEN	g_hPen[NUI_SKELETON_COUNT] = {NULL, };

POINT g_Point[NUI_SKELETON_POSITION_COUNT];
const COLORREF g_JointColorTable[NUI_SKELETON_POSITION_COUNT] = 
{
	RGB(169, 176, 155),	RGB(169, 176, 155),
	RGB(168, 230,  29),	RGB(200,   0,   0),
	RGB( 79,  84,  33),	RGB( 83,  33,  42),
	RGB(255, 126,   0),	RGB(215,  86,   0),
	RGB( 33,  79,  84),	RGB( 33,  33,  84),
	RGB( 77, 109, 243),	RGB( 37,  69, 243),
	RGB( 77, 109, 243),	RGB( 69,  33,  84),
	RGB(229, 170, 122),	RGB(255, 126,   0),
	RGB(181, 165, 213),	RGB( 71, 222,  76),
	RGB(245, 228, 156),	RGB( 77, 109, 243)
};

bool CreateNui()
{
	HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_SKELETON);
	if(FAILED(hr))
	{
		MessageBoxA(NULL, "NuiInitialize - Failed.", NULL, NULL);
		return false;
	}

	hr = NuiSkeletonTrackingEnable(g_hNextSkeletonEvent, 0);
	if(FAILED(hr))
	{
		MessageBoxA(NULL, "NuiSkeletonTrackingEnable - Failed.", NULL, NULL);
		return false;
	}

	g_hProcessStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

	g_hEvents[0] = g_hProcessStopEvent;
	g_hEvents[1] = g_hNextSkeletonEvent;

	ZeroMemory(&g_Point, sizeof(g_Point));

	g_hPen[0] = CreatePen(PS_SOLID, g_nWidth/80, RGB(255,0,0));
	g_hPen[1] = CreatePen(PS_SOLID, g_nWidth/80, RGB(0,255,0));
	g_hPen[2] = CreatePen(PS_SOLID, g_nWidth/80, RGB(0,255,255));
	g_hPen[3] = CreatePen(PS_SOLID, g_nWidth/80, RGB(255,255,0));
	g_hPen[4] = CreatePen(PS_SOLID, g_nWidth/80, RGB(255,0,255));
	g_hPen[5] = CreatePen(PS_SOLID, g_nWidth/80, RGB(0,0,255));

	HDC hDC = GetDC(g_hWnd);
	HBITMAP hBmp = CreateCompatibleBitmap(hDC, g_nWidth, g_nHeight);
	g_SkeletonDC = CreateCompatibleDC(hDC);
	SelectObject(g_SkeletonDC, hBmp);
	DeleteObject(hBmp);
	ReleaseDC(g_hWnd, hDC);

	return true;
}

void DrawSegment(POINT pt1, POINT pt2)
{
	MoveToEx(g_SkeletonDC, pt1.x, pt1.y, NULL);
	LineTo(g_SkeletonDC, pt2.x, pt2.y);
}

void DrawSkeleton()
{
	DrawSegment(g_Point[NUI_SKELETON_POSITION_HIP_CENTER], g_Point[NUI_SKELETON_POSITION_SPINE]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_SPINE], g_Point[NUI_SKELETON_POSITION_SHOULDER_CENTER]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_SHOULDER_CENTER], g_Point[NUI_SKELETON_POSITION_HEAD]);

	DrawSegment(g_Point[NUI_SKELETON_POSITION_SHOULDER_CENTER], g_Point[NUI_SKELETON_POSITION_SHOULDER_LEFT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_SHOULDER_LEFT], g_Point[NUI_SKELETON_POSITION_ELBOW_LEFT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_ELBOW_LEFT], g_Point[NUI_SKELETON_POSITION_WRIST_LEFT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_WRIST_LEFT], g_Point[NUI_SKELETON_POSITION_HAND_LEFT]);

	DrawSegment(g_Point[NUI_SKELETON_POSITION_SHOULDER_CENTER], g_Point[NUI_SKELETON_POSITION_SHOULDER_RIGHT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_SHOULDER_RIGHT], g_Point[NUI_SKELETON_POSITION_ELBOW_RIGHT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_ELBOW_RIGHT], g_Point[NUI_SKELETON_POSITION_WRIST_RIGHT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_WRIST_RIGHT], g_Point[NUI_SKELETON_POSITION_HAND_RIGHT]);

	DrawSegment(g_Point[NUI_SKELETON_POSITION_HIP_CENTER], g_Point[NUI_SKELETON_POSITION_HIP_LEFT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_HIP_LEFT], g_Point[NUI_SKELETON_POSITION_KNEE_LEFT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_KNEE_LEFT], g_Point[NUI_SKELETON_POSITION_ANKLE_LEFT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_ANKLE_LEFT], g_Point[NUI_SKELETON_POSITION_FOOT_LEFT]);

	DrawSegment(g_Point[NUI_SKELETON_POSITION_HIP_CENTER], g_Point[NUI_SKELETON_POSITION_HIP_RIGHT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_HIP_RIGHT], g_Point[NUI_SKELETON_POSITION_KNEE_RIGHT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_KNEE_RIGHT], g_Point[NUI_SKELETON_POSITION_ANKLE_RIGHT]);
	DrawSegment(g_Point[NUI_SKELETON_POSITION_ANKLE_RIGHT], g_Point[NUI_SKELETON_POSITION_FOOT_RIGHT]);

	for(int j = 0; j < NUI_SKELETON_POSITION_COUNT; j++)
	{
		HPEN hPen, OldPen;
					
		hPen = CreatePen(PS_SOLID, 9, g_JointColorTable[j]);
		OldPen = (HPEN)SelectObject(g_SkeletonDC, hPen);
					
		Ellipse(g_SkeletonDC, g_Point[j].x-5, g_Point[j].y-5, g_Point[j].x+5, g_Point[j].y+5);
					
		SelectObject(g_SkeletonDC, OldPen);
		DeleteObject(hPen);
	}

	HDC hDC = GetDC(g_hWnd);
	BitBlt(hDC, 0, 0, g_nWidth, g_nHeight, g_SkeletonDC, 0, 0, SRCCOPY);
	ReleaseDC(g_hWnd, hDC);	
}

void UpdateNui()
{
	int nEventIndex = WaitForMultipleObjects(g_nEventCount, g_hEvents, FALSE, 100);
	
	if(nEventIndex == 0)
		return;
	else
	{
		NUI_SKELETON_FRAME SkeletonFrame;
		NuiSkeletonGetNextFrame(0, &SkeletonFrame);
		
		bool bFoundSkeleton = false;
		for(int i = 0; i < NUI_SKELETON_COUNT; i++)
		{
			if(SkeletonFrame.SkeletonData[i].eTrackingState == NUI_SKELETON_TRACKED)
			{
				bFoundSkeleton = true;
				break;
			}
		}
		if(!bFoundSkeleton)	return;

		NuiTransformSmooth(&SkeletonFrame, NULL);
		PatBlt(g_SkeletonDC, 0, 0, g_nWidth, g_nHeight, BLACKNESS);

		for(int i = 0; i < NUI_SKELETON_COUNT; i++)
		{
			NUI_SKELETON_DATA* pSkeletonData = &SkeletonFrame.SkeletonData[i];

			if(pSkeletonData->eTrackingState == NUI_SKELETON_TRACKED)
			{
				float pfx = 0, pfy = 0;

				for(int j = 0; j < NUI_SKELETON_POSITION_COUNT; j++)
				{
					NuiTransformSkeletonToDepthImageF(pSkeletonData->SkeletonPositions[j], &pfx, &pfy);
					g_Point[j].x = (int)(pfx * g_nWidth + 0.5f);
					g_Point[j].y = (int)(pfy * g_nHeight + 0.5f);
				}

				SelectObject(g_SkeletonDC, g_hPen[i%NUI_SKELETON_COUNT]);
				DrawSkeleton();
			}
		}
	}
}

void DestroyNui()
{
	if(g_hProcessStopEvent)
	{
		CloseHandle(g_hProcessStopEvent);
		g_hProcessStopEvent = NULL;
	}

	NuiShutdown();

	if(g_hNextSkeletonEvent && (g_hNextSkeletonEvent != INVALID_HANDLE_VALUE))
	{
		CloseHandle(g_hNextSkeletonEvent);
		g_hNextSkeletonEvent = NULL;
	}

	for(int i = 0; i < NUI_SKELETON_COUNT; i ++)
	{
		if(g_hPen[i])
		{
			DeleteObject(g_hPen[i]);
			g_hPen[i] = NULL;
		}
	}

	if(g_SkeletonDC)
	{
		DeleteDC(g_SkeletonDC);
		g_SkeletonDC = NULL;
	}
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	}

	return DefWindowProcA(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
	LPCSTR lpClassName = "ex4_Skeleton";

	WNDCLASSA wc = { CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0,
		hInstance, LoadIcon(NULL, IDI_APPLICATION), LoadCursor(NULL, IDC_ARROW),
		(HBRUSH)GetStockObject(BLACK_BRUSH), NULL, lpClassName};
	RegisterClassA(&wc);

	RECT	rtWnd = {0, 0, g_nWidth, g_nHeight};
	DWORD	dwStyle = WS_OVERLAPPEDWINDOW;
	AdjustWindowRect(&rtWnd, dwStyle, FALSE);

	g_hWnd = CreateWindowA(lpClassName, lpClassName, dwStyle,
		CW_USEDEFAULT, CW_USEDEFAULT, rtWnd.right - rtWnd.left, rtWnd.bottom - rtWnd.top,
		NULL, NULL, wc.hInstance, NULL);
	ShowWindow(g_hWnd, SW_SHOW);
	UpdateWindow(g_hWnd);

	MSG msg;
	ZeroMemory(&msg, sizeof(MSG));

	if(CreateNui())
	{
		while(msg.message != WM_QUIT)
		{
			if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			else
				UpdateNui();
		}
		DestroyNui();
	}

	UnregisterClassA(lpClassName, wc.hInstance);
	return (int)msg.wParam;
}